﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Runtime.Serialization;
using System.Windows.Forms;
using Kawanon.Foundation;
using Kawanon.Localization;
using Kawanon.QtHoney;
using Kawanon.Usability.Actions;
using QTPlugin;
using QTPlugin.Interop;
using _IDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using Microsoft.Win32;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;

namespace Kawanon.Usability
{
	public class ActionEditor : UITypeEditor
	{
		public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
		{
			IList<GestureExpression> exprs = GestureSettings.LoadAllExpressions();

			List<Action> actions = new List<Action>();
			foreach (GestureExpression expr in exprs)
			{
				actions.Add(expr.Action);
			}

			ChooseFromArrayForm form = new ChooseFromArrayForm();
			form.Source = actions.ToArray();
			//form.Stringizer = delegate(object obj)
			//{
			//    Action a = (Action)obj;
			//    return a.Name;
			//};

			if (form.ShowDialog() == DialogResult.Cancel)
			{
				return value;
			}
			Action action = (Action)form.Selected;
			return action;
		}

		public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
		{
			return UITypeEditorEditStyle.Modal;
		}
	}

	public class PopupMenuFactory
	{
		private delegate void Generator(ToolStripItemCollection items);

		public IPluginServer Server { get; set; }
		public Workspace Workspace { get; set; }

		private Dictionary<PopupMenus, Generator> m_gens = new Dictionary<PopupMenus, Generator>();

		public PopupMenuFactory()
		{
			m_gens.Add(PopupMenus.Group, SelectFirst(ForGroup));
			m_gens.Add(PopupMenus.History, ForHistory);
			m_gens.Add(PopupMenus.Tabs, ForTabs);
			m_gens.Add(PopupMenus.RecentlyClosed, ForRecentlyClosed);
			m_gens.Add(PopupMenus.Application, ForApplication);
		}

		private static Generator SelectFirst(Generator gen)
		{
			return delegate(ToolStripItemCollection items)
			{
				int origin = items.Count;

				gen(items);

				if (items.Count > origin)
				{
					items[origin].Select();
				}
			};
		}

		private ToolStripMenuItem CreateAddressItem(Address addr)
		{
			byte[] bytes = Utility.AddressToPidl(addr);
			string dispName = Utility.GetDisplayName(bytes);
			Bitmap bitmap = Utility.GetIconBitmap(bytes, false);
			ToolStripMenuItem item = new ToolStripMenuItem(dispName, bitmap);
			return item;
		}

		private void ForTabs(ToolStripItemCollection items)
		{
			if (Server == null)
			{
				return;
			}
			ITab[] tabs = Server.GetTabs();
			Array.ForEach(tabs, delegate(ITab tab)	// foreach は tab が最後の要素になるので
			{
				ToolStripMenuItem item = CreateAddressItem(tab.Address);
				item.Click += Utility.ToEventHandler(delegate()
				{
					Server.SelectedTab = tab;
				});
				items.Add(item);

				if (tab.Selected)
				{
					item.Select();
					item.Font = new Font(item.Font, FontStyle.Bold);
				}
			});
		}

		private void ForHistory(ToolStripItemCollection items)
		{
			if (Server == null || Server.SelectedTab == null)
			{
				return;
			}
			ITab tab = Server.SelectedTab;
			List<Address> addrs = new List<Address>();
			addrs.AddRange(tab.GetHistory(true));

			int selectedIndex = addrs.Count;

			addrs.Add(Server.SelectedTab.Address);
			addrs.AddRange(tab.GetHistory(false));

			int origin = items.Count;

			foreach (Address addr in addrs)
			{
				int index = items.Count;

				ToolStripMenuItem item = CreateAddressItem(addr);
				item.Click += Utility.ToEventHandler(delegate()
				{
					int diff = index - selectedIndex;
					if (diff > 0)
					{
						Server.ExecuteCommand(Commands.GoForward, diff);
					}
					else if (diff < 0)
					{
						Server.ExecuteCommand(Commands.GoBack, -diff);
					}
				});
				items.Add(item);

				if (index == origin + selectedIndex)
				{
					item.Select();
					item.Font = new Font(item.Font, FontStyle.Bold);
				}
			}
		}

		private void ForGroup(ToolStripItemCollection items)
		{
			GroupMenuFactory factory = new GroupMenuFactory();
			factory.Expandable = false;
			factory.Server = Server;
			items.AddRange(factory.CreateItems());
		}

		//IMPORT: QTUtility2
		public static T[] ReadRegBinary<T>(string regValueName, RegistryKey rkUserApps)
		{
			byte[] buffer;
			try
			{
				buffer = (byte[])rkUserApps.GetValue(regValueName, null);
			}
			catch
			{
				return null;
			}
			if ((buffer != null) && (buffer.Length > 0))
			{
				using (MemoryStream stream = new MemoryStream(buffer))
				{
					BinaryFormatter formatter = new BinaryFormatter();
					return (T[])formatter.Deserialize(stream);
				}
			}
			return null;
		}

		private void ForRecentlyClosed(ToolStripItemCollection items)
		{
			using (RegistryKey key = Registry.CurrentUser.OpenSubKey(
				@"Software\Quizo\QTTabBar\RecentlyClosed", false))
			{
				if (key == null)
				{
					return;
				}

				// 値の番号は無視してもいいらしい
				string[] paths = Array.ConvertAll(key.GetValueNames(),
					(name) => key.GetValue(name).ToString());

				ToolStripItem[] a = Array.ConvertAll(paths,
					(path) =>
					{
						ToolStripMenuItem item = new ToolStripMenuItem();
						item.Text = Utility.GetDisplayName(path);
						item.Image = Utility.GetIconBitmap(path, false);
						item.Click += new EventHandler((sender, e) =>
						{
							Server.CreateTab(new Address(path), -1, false, true);
						});
						return item;
					});
				Array.Reverse(a);
				items.AddRange(a);
			}
		}

		private void InitializeApplicationItem(ToolStripMenuItem item, string[] strArray)
		{
			string[] paths = Array.ConvertAll(strArray,
				(s) => Environment.ExpandEnvironmentVariables(s));

			item.Image = Utility.GetIconBitmap(paths[0], false);
			item.Click += new EventHandler((sender, e) =>
			{
				Address currentFolder = new Address();
				if (this.Server != null)
				{
					currentFolder = this.Server.SelectedTab.Address;
				}
				Address[] addrs = null;
				_IDataObject data = Workspace.QuerySelection() as _IDataObject;
				if (data != null)
				{
					addrs = Utility.GetAddresses(data);
				}

				PathExpander expander = new PathExpander(currentFolder, addrs);
				string[] param = Array.ConvertAll(paths, (s) => expander.Replace(s));

				ProcessStartInfo info = new ProcessStartInfo();
				info.FileName = param[0];
				info.Arguments = param[1];
				info.WorkingDirectory = param[2];
				info.ErrorDialog = true;
				if (this.Server != null)
				{
					info.ErrorDialogParentHandle = this.Server.ExplorerHandle;
				}
				try
				{
					Process.Start(info);
				}
				catch
				{
				}
			});
		}

		//IMPORT: QTUtility
		private void CreateApplicationItems(ToolStripItemCollection items, string regPath)
		{
			using (RegistryKey key = Registry.CurrentUser.OpenSubKey(regPath, false))
			{
				if (key == null)
				{
					return;
				}
				foreach (string str in key.GetValueNames())
				{
					if (str.Length == 0)
					{
						continue;
					}
					string[] strArray = ReadRegBinary<string>(str, key);
					if ((strArray != null) && ((strArray.Length == 3) || (strArray.Length == 4)))
					{
						ToolStripMenuItem item = new ToolStripMenuItem(str);
						InitializeApplicationItem(item, strArray);
						items.Add(item);
					}
					else
					{
						using (RegistryKey key2 = key.OpenSubKey(str, false))
						{
							if (key2 != null)
							{
								ToolStripMenuItem item = new ToolStripMenuItem(str);
								CreateApplicationItems(item.DropDownItems, Path.Combine(regPath, str));
								items.Add(item);
							}
						}
					}
				}
			}
		}

		private void ForApplication(ToolStripItemCollection items)
		{
			CreateApplicationItems(items, @"Software\Quizo\QTTabBar\UserApps");
		}

		public void CreateItems(ToolStripItemCollection items, PopupMenus menu)
		{
			Generator gen;
			if (!m_gens.TryGetValue(menu, out gen))
			{
				return;
			}

			gen(items);
		}
	}

	//public abstract class PopupMenu
	//{
	//    public abstract ContextMenuStrip CreateInstance();
	//}

	[TypeConverter(typeof(EnumDisplayNameConverter))]
	public enum PopupMenus
	{
		[LocalizedEnumDisplayName(0)]
		None,

		[LocalizedEnumDisplayName(1)]
		Tabs,

		[LocalizedEnumDisplayName(2)]
		History,

		[LocalizedEnumDisplayName(3)]
		Group,

		[LocalizedEnumDisplayName(4)]
		RecentlyClosed,

		[LocalizedEnumDisplayName(5)]
		Application,
	}

	[TypeConverter(typeof(EnumDisplayNameConverter))]
	[Flags]
	public enum WheelDirection
	{
		[LocalizedEnumDisplayName(0)]
		None = 0,

		[LocalizedEnumDisplayName(1)]
		Up = 1,

		[LocalizedEnumDisplayName(2)]
		Down = 2,

		[LocalizedEnumDisplayName(3)]
		Both = Up | Down,
	}

	[TypeConverter(typeof(EnumDisplayNameConverter))]
	public enum TriggerButtons
	{
		[LocalizedEnumDisplayName(0)]
		None = MouseButtons.None,

		[LocalizedEnumDisplayName(1)]
		Left = MouseButtons.Left,

		[LocalizedEnumDisplayName(2)]
		Middle = MouseButtons.Middle,

		[LocalizedEnumDisplayName(3)]
		Right = MouseButtons.Right,

		[LocalizedEnumDisplayName(4)]
		XButton1 = MouseButtons.XButton1,

		[LocalizedEnumDisplayName(5)]
		XButton2 = MouseButtons.XButton2,
	}

	public sealed class WheelEventArgs : GestureEventArgs
	{
		public TriggerButtons Button { get; private set; }
		public WheelDirection Direction { get; private set; }

		public WheelEventArgs(TriggerButtons button, WheelDirection direction, GestureExpression expr) : base(expr)
		{
			Button = button;
			Direction = direction;
		}
	}

	public delegate void WheelEventHandler(object sender, WheelEventArgs e);

	[Serializable]
	public class RockerExpression
	{
		[LocalizedDisplayName(typeof(RockerExpression), 0)]
		public TriggerButtons PrimaryButton { get; set; }

		[LocalizedDisplayName(typeof(RockerExpression), 1)]
		public TriggerButtons SecondaryButton { get; set; }

		[LocalizedDisplayName(typeof(RockerExpression), 2)]
		[Editor(typeof(ActionEditor), typeof(UITypeEditor))]
		public Action Action { get; set; }

		public override string ToString()
		{
			return String.Format("{0}>{1}  {2}",
				GestureExpression.GetButtonSymbol(PrimaryButton),
				GestureExpression.GetButtonSymbol(SecondaryButton),
				Action != null ? Action.Name : String.Empty).Trim();
		}
	}

	[Serializable]
	public class RockerExpressionCollection : List<RockerExpression>
	{
	}

	[Serializable]
	public class WheelExpression
	{
		private enum Categories
		{
			Gesture = 0,
			Action,
		}

		private PopupMenus m_menu;
		private Action m_action;

		[LocalizedCategory(typeof(WheelExpression), (int)Categories.Gesture)]
		[LocalizedDisplayName(typeof(WheelExpression), 0)]
		//[DefaultValue(TriggerButtons.Right)]
		public TriggerButtons Button { get; set; }

		[LocalizedCategory(typeof(WheelExpression), (int)Categories.Gesture)]
		[LocalizedDisplayName(typeof(WheelExpression), 1)]
		//[DefaultValue(WheelDirection.Both)]
		public WheelDirection Direction { get; set; }

		[LocalizedCategory(typeof(WheelExpression), (int)Categories.Action)]
		[LocalizedDisplayName(typeof(WheelExpression), 2)]
		//[DefaultValue(PopupMenus.Tabs)]
		public PopupMenus Menu
		{
			get
			{
				return m_menu;
			}
			set
			{
				if (value != PopupMenus.None)
				{
					m_action = null;
				}
				m_menu = value;
			}
		}

		[LocalizedCategory(typeof(WheelExpression), (int)Categories.Action)]
		[LocalizedDisplayName(typeof(WheelExpression), 3)]
		[Editor(typeof(ActionEditor), typeof(UITypeEditor))]
		//[DefaultValue(null)]
		public Action Action
		{
			get
			{
				return m_action;
			}
			set
			{
				if (value != null)
				{
					m_menu = PopupMenus.None;
				}
				m_action = value;
			}
		}

		public WheelExpression()
		{
			Button = TriggerButtons.Right;
			Direction = WheelDirection.Both;
			Menu = PopupMenus.Tabs;
		}

		public WheelExpression(TriggerButtons button, WheelDirection direction, PopupMenus menu)
		{
			Button = button;
			Direction = direction;
			Menu = menu;
		}

		public override string ToString()
		{
			string behavior = String.Empty;
			if (Menu != PopupMenus.None)
			{
				behavior = Reflection.GetEnumDisplayName(Menu);
			}
			else if (Action != null)
			{
				behavior = Action.Name;
			}

			return String.Format("{0}({1}) {2}",
				Reflection.GetEnumDisplayName(Button), Reflection.GetEnumDisplayName(Direction), behavior).Trim();
		}
	}

	// LocalSerializationBinder でジェネリック型は解決しにくいので、明示的にアセンブリ内で型を作る
	[Serializable]
	public class WheelExpressionCollection : List<WheelExpression>
	{
	}

	[Serializable]
	public class GestureInformation
	{
		[ResourceKey("GestureInformation_Category")]
		private enum Categories
		{
			General = 0,
			WheelGesture,
			RockerGesture,
		}

		private const int DEFAULT_MINIMUM_DISTANCE = 10;

		private WheelExpressionCollection m_wheelExpressions;
		private RockerExpressionCollection m_rockerExpressions;
		private int m_minimumDistance;

		[LocalizedDisplayName(typeof(GestureInformation), 0)]
		[LocalizedCategory(Categories.General)]
		[TypeConverter(typeof(YesNoConverter))]
		[DefaultValue(true)]
		public bool Enabled { get; set; }

		[LocalizedDisplayName(typeof(GestureInformation), 1)]
		[LocalizedDescription(typeof(GestureInformation), 1)]
		[LocalizedCategory(Categories.General)]
		[DefaultValue(DEFAULT_MINIMUM_DISTANCE)]
		public int MinimumDistance
		{
			get
			{
				if (m_minimumDistance <= 0)
				{
					return DEFAULT_MINIMUM_DISTANCE;
				}
				return m_minimumDistance;
			}
			set
			{
				if (value > 0)
				{
					m_minimumDistance = value;
				}
				else
				{
					throw new ArgumentException();
				}
			}
		}

		[LocalizedDisplayName(typeof(GestureInformation), 2)]
		[LocalizedCategory(Categories.General)]
		[TypeConverter(typeof(YesNoConverter))]
		[DefaultValue(true)]
		public bool DrawTrails { get; set; }

		[LocalizedDisplayName(typeof(GestureInformation), 3)]
		[LocalizedDescription(typeof(GestureInformation), 3)]
		[LocalizedCategory(Categories.General)]
		[TypeConverter(typeof(YesNoConverter))]
		[DefaultValue(false)]
		public bool PreferGestureTrack { get; set; }

		[LocalizedDisplayName(typeof(GestureInformation), 4)]
		[LocalizedDescription(typeof(GestureInformation), 4)]
		[LocalizedCategory(Categories.WheelGesture)]
		[Editor(typeof(MyCollectionEditor), typeof(UITypeEditor))]
		[TypeConverter(typeof(MyCollectionConverter))]
		[DefaultValue(null)]
		public WheelExpressionCollection WheelExpressions
		{
			get
			{
				if (m_wheelExpressions == null)
				{
					m_wheelExpressions = new WheelExpressionCollection();
					// デフォルト定義
					m_wheelExpressions.Add(
						new WheelExpression(TriggerButtons.Right, WheelDirection.Up, PopupMenus.History));
					m_wheelExpressions.Add(
						new WheelExpression(TriggerButtons.Right, WheelDirection.Down, PopupMenus.Tabs));
				}
				return m_wheelExpressions;
			}
			set
			{
				m_wheelExpressions = value;
			}
		}

		[LocalizedDisplayName(typeof(GestureInformation), 4)]
		[LocalizedCategory(Categories.RockerGesture)]
		[Editor(typeof(MyCollectionEditor), typeof(UITypeEditor))]
		[TypeConverter(typeof(MyCollectionConverter))]
		[DefaultValue(null)]
		public RockerExpressionCollection RockerExpressions
		{
			get
			{
				if (m_rockerExpressions == null)
				{
					m_rockerExpressions = new RockerExpressionCollection();
				}
				return m_rockerExpressions;
			}
			set
			{
				m_rockerExpressions = value;
			}
		}

		public GestureInformation()
		{
			MinimumDistance = DEFAULT_MINIMUM_DISTANCE;
			Enabled = true;
			DrawTrails = true;
			PreferGestureTrack = false;
			WheelExpressions = null;
			RockerExpressions = null;
		}
	}

	public class GestureDescription
	{
		public string Category { get; private set; }
		public bool IsAcceptTarget { get; private set; }

		public GestureDescription(string category, bool isAcceptTarget)
		{
			Category = category;
			IsAcceptTarget = isAcceptTarget;
		}
	}

	public class GestureEventArgs : EventArgs
	{
		public GestureExpression Expression { get; private set; }
		public IList<GestureExpression> Suggestions { get; private set; }

		public GestureEventArgs(GestureExpression expr)
		{
			Expression = expr;
		}

		public GestureEventArgs(GestureExpression expr, IList<GestureExpression> suggestions)
		{
			Expression = expr;
			Suggestions = suggestions;
		}
	}

	public delegate void GestureEventHandler(object sender, GestureEventArgs e);

	[Serializable]
	public abstract class Action
	{
		public class Arguments
		{
			public IPluginServer Server { get; set; }
			public IPluginClient Plugin { get; set; }
			public IShellView ShellView { get; set; }
			public IntPtr ViewHandle { get; set; }
			public object Target { get; set; }
			// ホイールジェスチャの方向によって逆の動作をする
			public bool Reverse { get; set; }

			// コマンドの実行後も選択を保持する
			public bool KeepSelection { get; set; }
		}

		[NonSerialized]
		private string m_nameCache;

		protected Address[] GetAddresses(object target)
		{
			_IDataObject data = target as _IDataObject;
			if (data != null)
			{
				return Utility.GetAddresses(data);
			}
			Address[] addrs = target as Address[];
			if (addrs != null)
			{
				return addrs;
			}
			return null;
		}

		protected string[] GetFileNames(object target)
		{
			_IDataObject data = target as _IDataObject;
			if (data != null)
			{
				return Utility.GetFileNames(data);
			}
			string[] paths = target as string[];
			if (paths != null)
			{
				return paths;
			}
			return null;
		}

		protected virtual string GetName()
		{
			string typeName = this.GetType().Name;
			StdActions val;
			try
			{
				val = (StdActions)Enum.Parse(typeof(StdActions), typeName);
			}
			catch (ArgumentException)
			{
				throw new NotImplementedException("Name property is must override");
			}
			return StringResource.Localize(val, false);
		}

		public virtual string Name
		{
			get
			{
				if (m_nameCache == null)
				{
					m_nameCache = GetName();
				}
				return m_nameCache;
			}
		}

		public abstract void Execute(Arguments args);

		//protected Action(string name)
		//{
		//    Name = name;
		//}

		public override bool Equals(object obj)
		{
			if (!(obj is Action))
			{
				return false;
			}
			//NOTE: 各クラスで GetHashCode にタイプのハッシュを含めるので不要
			//if (this.GetType() != obj.GetType())
			//{
			//    return false;
			//}
			if (GetHashCode() != obj.GetHashCode())
			{
				return false;
			}

			return true;
		}

		public override int GetHashCode()
		{
			//デフォルトでは同じ型は同じハッシュで
			return ~ this.GetType().GetHashCode();
		}

		//protected int InternalGetHashCode()
		//{
		//    return base.GetHashCode();
		//}

		public override string ToString()
		{
			return this.Name;
		}

		public static Action From(Commands command, object arg)
		{
			return new BuiltInCommand(command, arg);
		}

		public static Action From(Commands command)
		{
			return new BuiltInCommand(command, null);
		}
	}

	[Serializable]
	public abstract class CustomAction : Action
	{
		private static readonly Random rand = new Random();

		public int Id { get; private set; }

		public virtual bool IsAcceptTarget { get { return false; } }

		public abstract DialogResult ShowEditDialog(IntPtr hwnd);

		public CustomAction()
		{
			Id = rand.Next(Int32.MinValue, Int32.MaxValue);
		}

		//public override int GetHashCode()
		//{
		//    throw new NotSupportedException();
		//}

		public override int GetHashCode()
		{
			//NOTE: ShowEditDialog によって内容が変化するため、同一性をIDとして保持
			return Id;
		}

		protected DialogResult ShowPropertiesForm<T>(IntPtr hwnd, string title, Bitmap image, ref T obj, Predicate<T> validator)
		{
			PropertiesForm form = new PropertiesForm();
			form.Text = title;
			if (image != null)
			{
				form.SetIcon(image);
			}
			form.SelectedObject = obj;
			if (form.ShowDialog(Form.FromHandle(hwnd)) == DialogResult.Cancel)
			{
				return DialogResult.Cancel;
			}
			T ret = (T)form.SelectedObject;
			if (!validator(ret))
			{
				return DialogResult.Cancel;
			}

			obj = ret;	// 値を反映
			return DialogResult.OK;
		}
	}

	[Serializable]
	internal class BuiltInCommand : Action
	{
		//[DllImport("shell32.dll")]
		//[return: MarshalAs(UnmanagedType.Bool)]
		//public static extern bool SHGetPathFromIDListW(IntPtr pidl, [MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszPath);

		//private delegate object TargetTranslator(BuiltInCommand cmd, Action.Arguments args);
		private delegate bool Injector(BuiltInCommand cmd, Action.Arguments args);

		//private static Dictionary<Commands, TargetTranslator> s_translators = new Dictionary<Commands, TargetTranslator>();
		private static Dictionary<Commands, Injector> s_injectors = new Dictionary<Commands, Injector>();


		static BuiltInCommand()
		{
			//NOTE: 過去の保存値とハッシュが変わってしまうので使わない
			//s_translators.Add(Commands.GoBack, TargetIsOne);
			//s_translators.Add(Commands.GoForward, TargetIsOne);

			s_injectors.Add(Commands.GoBack, Injector_GoBack);
			s_injectors.Add(Commands.GoForward, Injector_GoForward);
			s_injectors.Add(Commands.CloseCurrentTab, Injector_CloseCurrentTab);
			s_injectors.Add(Commands.MD5, Injector_TargetIsStringArray);
		}

		private static bool Injector_GoBack(BuiltInCommand cmd, Action.Arguments args)
		{
			args.Server.ExecuteCommand(args.Reverse ? Commands.GoForward : Commands.GoBack, cmd.m_arg);
			return true;
		}

		private static bool Injector_GoForward(BuiltInCommand cmd, Action.Arguments args)
		{
			args.Server.ExecuteCommand(args.Reverse ? Commands.GoBack : Commands.GoForward, cmd.m_arg);
			return true;
		}

		private static bool Injector_CloseCurrentTab(BuiltInCommand cmd, Action.Arguments args)
		{
			ITab[] tabs = args.Server.GetTabs();
			if (tabs.Length == 1)
			{
				args.Server.ExecuteCommand(Commands.CloseWindow, null);
				return true;
			}
			else
			{
				return false;
			}
		}

		private static bool Injector_TargetIsStringArray(BuiltInCommand cmd, Action.Arguments args)
		{
			args.Server.ExecuteCommand(cmd.m_command, cmd.GetFileNames(args.Target));
			return true;
		}

		//private static object TargetIsOne(object target)
		//{
		//    return 1;
		//}

		//private static object TargetToAddressArray(BuiltInCommand cmd, Action.Arguments args)
		//{
		//    return cmd.GetAddresses(args.Target);
		//}

		private Commands m_command;
		private object m_arg;	// 固定パラメータ

		public BuiltInCommand(Commands command, object arg)
		{
			m_command = command;
			m_arg = arg;
		}

		protected override string GetName()
		{
			return StringResource.Localize("BuiltInCommand_DisplayName", (int)m_command, false);
		}

		public override void Execute(Arguments args)
		{
			//object arg = m_arg;
			//if (arg == null)
			//{
			//    TargetTranslator translator = null;
			//    if (s_translators.TryGetValue(m_command, out translator))
			//    {
			//        arg = translator.Invoke(this, args);
			//    }
			//}

			Injector injector = null;
			if (s_injectors.TryGetValue(m_command, out injector))
			{
				if (injector(this, args))
				{
					return;
				}
			}
			args.Server.ExecuteCommand(m_command, m_arg);
		}

		public override int GetHashCode()
		{
			//NOTE: enum のハッシュは0からのインデックスなので、m_argとは重みを変える
			return GetType().GetHashCode() ^ (m_command.GetHashCode() << 16) ^ (m_arg != null ? m_arg.GetHashCode() : 0);
		}
	}

	[Serializable]
	public class GestureExpression
	{
		public static readonly int[] EmptyTrails = new int[0];

		private static readonly string Urdl = StringResource.Localize(MiscResources.MouseDirectionSymbol, "URDL");

		private static readonly Keys[] s_supportedKeys = new Keys[] {
			Keys.RButton, Keys.MButton, Keys.XButton1, Keys.XButton2 };

		public static IList<Keys> GetSupportedButtons()
		{
			return Array.AsReadOnly(s_supportedKeys);
		}

		public virtual string Name
		{
			get
			{
				if (Action != null)
				{
					return Action.Name;
				}
				else
				{
					return null;
				}
			}
		}

		public int[] Trails { get; private set; }
		public Action Action { get; private set; }
		public Keys Button
		{
			get
			{
				if (m_button == Keys.None)	// 旧バージョンとの互換性のため
				{
					return Keys.RButton;
				}
				return m_button;
			}
			private set
			{
				if (Array.IndexOf(s_supportedKeys, value) == -1)
				{
					throw new ArgumentException();
				}
				m_button = value;
			}
		}

		[OptionalField]
		private Keys m_button;

		public string CommandString
		{
			get
			{
				return Button.ToString() + '+' + FormatTrails("URDL");
			}
		}

		public GestureExpression(int[] trails, Action action, Keys button)
		{
			Trails = trails;
			Action = action;
			Button = button;
		}

		public GestureExpression(string urdl, Action action, Keys button)
			: this(Parse(urdl), action, button)
		{
		}

		public GestureExpression(int[] trails, Action action)
			: this(trails, action, Keys.RButton)
		{
		}

		public GestureExpression(string urdl, Action action)
			: this(Parse(urdl), action, Keys.RButton)
		{
		}

		public GestureExpression(int[] trails, GestureExpression expr)
			:this(trails, expr.Action, expr.Button)
		{
		}

		public GestureExpression(string urdl, GestureExpression expr)
			: this(Parse(urdl), expr.Action, expr.Button)
		{
		}

		public GestureExpression(Action action, GestureExpression expr)
			: this(expr.Trails, action, expr.Button)
		{
		}

		private static int[] Parse(string urdl)
		{
			//if (urdl.Length == 0)
			//{
			//    throw new FormatException("string is empty");
			//}
			int[] trails = Array.ConvertAll(urdl.ToCharArray(), new Converter<char, int>(
				delegate(char c)
				{
					switch (Char.ToUpper(c))
					{
						case 'U':
					//	case '↑':
							return 0;
						case 'R':
					//	case '→':
							return 1;
						case 'D':
					//	case '↓':
							return 2;
						case 'L':
					//	case '←':
							return 3;
					}

					int index = Urdl.IndexOf(c);
					if (index != -1)
					{
						return index;
					}
					throw new FormatException(c + " of " + urdl);
				}));

			return trails;
		}

		public static string FormatTrails(int[] trails, string urdl)
		{
			if (trails == null)
			{
				return String.Empty;
			}
			char[] ar = Array.ConvertAll(trails, new Converter<int, char>(
				delegate(int direction)
				{
					return urdl[direction];
				}));

			return new String(ar);
		}

		public static string FormatTrails(int[] trails)
		{
			return FormatTrails(trails, Urdl);
		}

		public string FormatTrails(string urdl)
		{
			return FormatTrails(Trails, urdl);
		}

		public string FormatTrails()
		{
			return FormatTrails(Trails);
		}

		public bool TrailsEquals(int[] trails)
		{
			if (Trails.Length != trails.Length)
			{
				return false;
			}
			for (int i = 0; i < Trails.Length; i++)
			{
				if (Trails[i] != trails[i])
				{
					return false;
				}
			}
			return true;
		}

		public bool TrailsEquals(GestureExpression expr)
		{
			return TrailsEquals(expr.Trails);
		}

		public override string ToString()
		{
			return String.Format("{0} {1}", Name, CommandString);
		}

		public static string GetButtonSymbol(TriggerButtons button)
		{
			IList<string> symbols = StringResource.Split("TriggerButtonsSymbol_Resource", 6, "?");
			LocalizedEnumDisplayNameAttribute attr =
				Reflection.GetEnumFieldAttribute<LocalizedEnumDisplayNameAttribute>(button);
			return symbols[attr.Index];
		}
	}
}
